/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.filter.headers;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;

/**
 * RemoveHopByHopHeadersFilter 是 Spring Cloud Gateway 中的一个实现类，它实现了 HttpHeadersFilter 接口。
 * 该过滤器的主要作用是移除 HTTP 请求或响应中的逐跳头（hop-by-hop headers）。
 * 逐跳头是指那些仅对单次传输有效，不应被网关或代理转发到下一跳的头部字段。
 *
 * 逐跳头的定义
 * 		逐跳头是根据 HTTP/1.1 规范定义的，它们仅适用于单次请求-响应链路中的相邻节点之间，
 * 		而不应该被中间的网关、代理等转发到最终的目标服务器或客户端。常见的逐跳头包括：
 *
 * 	Connection
 * 		描述：Connection 头用于控制当前连接的行为。
 * 		用途：
 * 			它可以指定是否应该关闭连接（close），或者是否可以保持打开状态以供后续请求使用（keep-alive）。
 * 			此外，还可以列出其他不应该被转发到下一个跳点的头部名称。
 * 	Keep-Alive
 * 		描述：尽管不是标准的HTTP/1.1头部，但在HTTP/1.0中广泛使用来维持持久连接。
 * 		用途：
 * 			提供关于持久连接的信息，如超时时间和最大请求数量。
 * 			在HTTP/1.1中，这种功能通常通过Connection: keep-alive实现。
 * 	Proxy-Authenticate
 * 		描述：当客户端试图访问需要身份验证的资源但未提供或提供了无效凭据时，服务器会返回此响应头。
 * 		用途：
 * 			指示客户端应使用的认证方案（如Basic、Digest等）以及代理服务器的身份验证域。
 * 	Proxy-Authorization
 * 		描述：由客户端发送给代理服务器以提供认证信息。
 * 		用途：
 * 			包含客户端凭据，以便通过代理服务器的身份验证机制。
 * 	TE
 * 		描述：指示客户端愿意接受的传输编码方式。
 * 		用途：
 * 			可能包括分块传输编码（chunked），以及其他可能的扩展编码类型。
 * 			帮助服务器了解客户端能够处理的数据传输格式。
 * 	Trailers
 * 		描述：类似于HTTP头部，但是位于实体主体之后。
 *		用途：
 * 			当使用分块传输编码时，允许发送者在消息末尾添加额外的元数据。
 * 			这些元数据不能依赖于正文内容的存在。
 * 	Transfer-Encoding
 * 		描述：指定了报文主体使用的编码形式。
 * 		用途：
 * 			最常见的值是chunked，表示数据将以一系列长度已知的小块形式传送。
 * 			允许动态地改变数据流的大小而不必预先知道整个数据的大小。
 * 	Upgrade
 * 		描述：允许客户端请求从一种协议切换到另一种协议。
 * 		用途：
 * 			最常见的是用于升级到WebSocket协议。
 * 			提供了一种机制让客户端和服务器协商并转换到更高效的通信协议。
 *
 * 	这些头部字段主要用于管理请求和响应如何在客户端、代理和服务器之间传递，并确保每个“跳”之间的正确交互。
 * 	由于它们只适用于直接相连的两方之间的通信，因此在经过网关或其他中间件时需要被移除或重新设置，以免影响后续的通信链路。
 * 	这就是为什么像RemoveHopByHopHeadersFilter这样的过滤器存在的原因，它负责清理这些不应该跨过多个跳点的头部信息。
 *
 * 	各自的角色
 * 		Connection
 * 			客户端和服务器：可以由客户端用于指定是否希望保持连接打开（例如，keep-alive），也可以由服务器指示连接将在响应后关闭（close）。此外，还可以列出不应被转发到下一个节点的头部名称。
 * 		Keep-Alive
 * 			主要由客户端：虽然不是标准HTTP/1.1的一部分，但在HTTP/1.0中用于请求持久连接。在HTTP/1.1中，通过Connection: keep-alive来实现类似的功能。
 * 		Proxy-Authenticate
 * 			服务器：当客户端尝试访问需要身份验证的资源但未提供或提供了无效凭据时，服务器会返回此响应头。它告诉客户端应该使用的认证方案以及代理的身份验证域。
 * 		Proxy-Authorization
 * 			客户端：客户端发送给代理服务器以提供认证信息，包含客户端凭据以便通过代理服务器的身份验证机制。
 * 		TE
 * 			客户端：客户端用这个头部告知服务器它可以接受的传输编码方式，比如分块传输编码(chunked)等。
 * 		Trailers
 * 			通常与服务器相关联：当使用分块传输编码时，允许发送者在消息末尾添加额外的元数据。不过，在某些情况下，客户端也可能需要处理或生成这样的trailers。
 * 		Transfer-Encoding
 * 			服务器：主要用于服务器端，用来指定报文主体使用的编码形式（如chunked），但客户端也可以在特定情况下使用它，比如在上传文件时采用流式传输。
 * 		Upgrade
 * 			客户端发起，服务器确认：客户端提出升级到不同的协议（如WebSocket），然后服务器响应是否同意升级。如果服务器同意，则会返回一个101 Switching Protocols状态码，并根据需要更改协议版本。
 */
@ConfigurationProperties("spring.cloud.gateway.filter.remove-hop-by-hop")
public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered {

	/**
	 * Headers to remove as the result of applying the filter.
	 */
	public static final Set<String> HEADERS_REMOVED_ON_REQUEST = new HashSet<>(
			Arrays.asList("connection", "keep-alive", "transfer-encoding", "te", "trailer", "proxy-authorization",
					"proxy-authenticate", "x-application-context", "upgrade"
			// these two are not listed in
			// https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3
			// "proxy-connection",
			// "content-length",
			));

	private int order = Ordered.LOWEST_PRECEDENCE - 1;

	private Set<String> headers = HEADERS_REMOVED_ON_REQUEST;

	public Set<String> getHeaders() {
		return headers;
	}

	public void setHeaders(Set<String> headers) {
		Assert.notNull(headers, "headers may not be null");
		this.headers = headers.stream().map(String::toLowerCase).collect(Collectors.toSet());
	}

	@Override
	public int getOrder() {
		return order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public HttpHeaders filter(HttpHeaders originalHeaders, ServerWebExchange exchange) {
		HttpHeaders filtered = new HttpHeaders();
		List<String> connectionOptions = originalHeaders.getConnection().stream().map(String::toLowerCase).toList();
		Set<String> headersToRemove = new HashSet<>(headers);
		headersToRemove.addAll(connectionOptions);

		for (Map.Entry<String, List<String>> entry : originalHeaders.entrySet()) {
			if (!headersToRemove.contains(entry.getKey().toLowerCase())) {
				filtered.addAll(entry.getKey(), entry.getValue());
			}
		}

		return filtered;
	}

	@Override
	public boolean supports(Type type) {
		return type.equals(Type.REQUEST) || type.equals(Type.RESPONSE);
	}

}
